package datacreation;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Random;
import java.util.Scanner;
import fileoutputobjects.Drifter;
import fileoutputobjects.HumanSimulator;
import fileoutputobjects.MutableInteger;
import guiobjects.Observation;
import java.lang.reflect.*;


import user.data.Data;
import user.functions.ShouldDrawObject;

/**
 * This class contains a method that creates a file that contains observations of several 
 * simulated human beings.
 * 
 * @author Trevor Terris
 *
 */
public class ObservationCreator
{
    //numFrames determines the duration of the simulation
    private static int numFrames = Data.numFrames;
    //numToBeSimulated determines the number of humans to be simulated
    private static int numToBeSimulated = Data.numToBeSimulated;
    private static double xNoise = Data.xNoise;
    private static double zNoise = Data.zNoise;
    private static double attributeNoise = Data.attributeNoise;

    //private static Random myRandom = new Random(0);
    private static Random myRandomSeedGenerator = new Random();
    private static int myRandomSeed = myRandomSeedGenerator.nextInt();
    private static Random myRandom = new Random(myRandomSeed);
    
    /**
     * This method creates the file of observations.
     * 
     * @throws IOException
     */
    public static ArrayList<Observation> createData(String dirName)
    {
    	if (Data.SET_RANDOM_SEED)
    	{
    		myRandom = new Random(Data.RANDOM_SEED);
    		myRandomSeed = Data.RANDOM_SEED;
    	}
    	ArrayList<Observation> toReturn = new ArrayList<Observation>();
    	int numAdded = 0;
    	try
    	{
	        //First, a list of simulations is created.
	        ArrayList<HumanSimulator> itemsToBeSimulated = new ArrayList<HumanSimulator>();
	        MutableInteger ID = new MutableInteger(0);
	        boolean hasPrintedWarning = false;
	        for (int k = 0; k < numToBeSimulated; k++)
	        {
	        	HumanSimulator h;
	        	try
	        	{
		        	Class<?> c = Class.forName("fileoutputobjects." + Data.outputObject);
		        	Constructor<?> struct = c.getConstructor(MutableInteger.class, Random.class);
		        	h = (HumanSimulator) struct.newInstance(ID, myRandom);
	        	}
	        	catch (Exception e)
	        	{
	        		if (!hasPrintedWarning)
	        			System.out.println("Error creating simulator object. Defaulting to Drifter.");
	        		hasPrintedWarning = true;
	        		h = new Drifter(ID, myRandom);
	        	}
	        	itemsToBeSimulated.add(h);
	        }

	        File seedFile = new File(Data.SEED_NAME);
	        File directory = new File(dirName);
	        if (!directory.exists())
	        	directory.mkdir();
	        //A new BufferedWriter is created
	        ArrayList<String> seedList = new ArrayList<String>();
	        if (!seedFile.exists())
	        	seedList.add("Folder name : random seed : time, date the seed was used\n");
	        else
	        {
		        Scanner seedScanner = new Scanner(seedFile);
		        while (seedScanner.hasNextLine())
		        	seedList.add(seedScanner.nextLine());
	        }
	        Writer seedOutput = null;
	        seedOutput = new BufferedWriter(new FileWriter(seedFile));
	        seedOutput.flush();
	        for (int i = 0; i < seedList.size(); i++)
	        	seedOutput.write(seedList.get(i) + "\n");

	        Calendar cal = Calendar.getInstance();
	        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss MM/dd/yyyy");
	        String time = sdf.format(cal.getTime());
	        seedOutput.write(dirName + " : " + Integer.toString(myRandomSeed) + " : " + time);
	        seedOutput.close();
	        //For each frame, each item is iterated over. 
	        ArrayList<ArrayList<Observation>> realNodes = new ArrayList<ArrayList<Observation>>();
	        ArrayList<ArrayList<Observation>> startNodes = new ArrayList<ArrayList<Observation>>();
	        ArrayList<ArrayList<Observation>> endNodes = new ArrayList<ArrayList<Observation>>();
	        ArrayList<Integer> toRemoveFromStart = new ArrayList<Integer>();
	        ArrayList<Integer> toRemoveFromEnd = new ArrayList<Integer>();
	        for (int i = 0; i < numFrames; i++)
	        {
	        	ArrayList<HumanSimulator> thisFrame = new ArrayList<HumanSimulator>();
	        	for (int j = 0; j < itemsToBeSimulated.size(); j++)
	        	{
	        		thisFrame.add(itemsToBeSimulated.get(j));
	        	}
        		ArrayList<Observation> realInFrame = new ArrayList<Observation>();
	        	for (int j = 0; j < Data.cameraList.size(); j++)
	        	{
		            for (int k = 0; k < itemsToBeSimulated.size(); k++)
		            {
		            	double actual = itemsToBeSimulated.get(k).trueXCoord();
		                double xVal = adjusted(actual, xNoise);
		                actual = itemsToBeSimulated.get(k).trueZCoord();
		                double zVal = adjusted(actual, zNoise);
		                double[] attributes = new double[itemsToBeSimulated.get(k).numAttributes()];
		                for (int l = 0; l < attributes.length; l++)
		                {
		                	actual = itemsToBeSimulated.get(k).getAttribute(l);
		                	attributes[l] = adjusted(actual, attributeNoise);
		                }
		                String toBeWritten = xVal + " " + zVal + " ";
		                for (int m = 0; m < attributes.length; m++)
		                {
		                	toBeWritten += attributes[m] + " ";
		                }
		                toBeWritten += itemsToBeSimulated.get(k).ID() + " " + j + " self\n";
		                
		                Observation thisObservation = new Observation(toBeWritten);
		                thisObservation.frameNum = 1 + i;
		                
		                if (ShouldDrawObject.shouldDraw(thisObservation) && 
		                		Data.cameraList.get(j).hasInView(thisObservation) && 
		                		Data.cameraList.get(j).isCorrectFrame(i+1) && 
		                		!(numAdded >= Data.numObservations && !Data.useFrameNum))
		                {
		    				realInFrame.add(thisObservation);
		    				numAdded++;
		                }
		
		                itemsToBeSimulated.get(k).step();
		            }
	        	}
	            
	            if (Data.USE_DUMMIES)
	            {
	            	int previousCam = 0;
	            	int nextCam = 0;
	            	for (int l = 0; l < Data.cameraList.size(); l++)
	            	{
	            		if (Data.cameraList.get(l).isCorrectFrame(i))
	            			previousCam = l;
	            		if (Data.cameraList.get(l).isCorrectFrame(i + 2))
	            			nextCam = l;
	            	}
	            	ArrayList<Observation> leading = new ArrayList<Observation>();
	            	ArrayList<Observation> ending = new ArrayList<Observation>();
	            	if (i == 0)
	            		toRemoveFromStart.add(startNodes.size());
	            	if (i == numFrames - 1)
	            		toRemoveFromEnd.add(endNodes.size());
	            	if (i != 0)
	            	{
	            		for (int l = 0; l < realInFrame.size(); l++)
	            		{
	            			int index = leading.size();
	            			leading.add((new Observation(realInFrame.get(l))));
	            			leading.get(index).camera = previousCam;
	            			leading.get(index).frameNum = leading.get(index).frameNum - 1;
	            			leading.get(index).isEntranceNode = true;
	            		}
	            	}
            		for (int l = 0; l < realInFrame.size(); l++)
            		{
            			int index = ending.size();
            			ending.add((new Observation(realInFrame.get(l))));
            			ending.get(index).camera = nextCam;
            			ending.get(index).frameNum = ending.get(index).frameNum + 1;
            			ending.get(index).isExitNode = true;
            		}
            		startNodes.add(leading);
            		endNodes.add(ending);
	            }
                
                realNodes.add(realInFrame);
	        }
	        
	        if (startNodes.size() == 0 && endNodes.size() == 0)
	        {
	        	for (int i = 0; i < realNodes.size(); i++)
	        		toReturn.addAll(realNodes.get(i));
	        	return toReturn;
	        }
	        
	        for (int i = toRemoveFromStart.size() - 1; i >= 0; i--)
	        	startNodes.remove((int)toRemoveFromStart.get(i));
	        for (int i = toRemoveFromEnd.size() - 1; i >= 0; i--)
	        	endNodes.remove((int)toRemoveFromEnd.get(i));

	        ArrayList<Integer> realStart = new ArrayList<Integer>();
	        ArrayList<Integer> startStart = new ArrayList<Integer>();
	        ArrayList<Integer> endStart = new ArrayList<Integer>();
	        for (int i = 0; i < endNodes.size(); i++)
	        {
	        	realStart.add(toReturn.size());
	        	toReturn.addAll(realNodes.get(i));
	        	startStart.add(toReturn.size());
	        	toReturn.addAll(startNodes.get(i));
	        	endStart.add(toReturn.size());
	        	toReturn.addAll(endNodes.get(i));
	        }
	        realStart.add(toReturn.size());
	        toReturn.addAll(realNodes.get(realNodes.size() - 1));
	        for (int i = 0; i < realStart.size(); i++)
	        {
	        	for (int j = 0; j < realNodes.get(i).size(); j++)
	        	{
	        		if (i != 0)
	        			toReturn.get(j + startStart.get(i - 1)).dummyOf = realStart.get(i) + j;
	        		if (i != realStart.size() - 1)
	        		{
	        			toReturn.get(j + endStart.get(i)).dummyOf = realStart.get(i) + j;
	        		}
	        	}
	        }
	        return toReturn;
    	}
    	catch (Exception e)
    	{
    		System.out.println("Error writing to file.");
    		e.printStackTrace();
    		return new ArrayList<Observation>();
    	}
    }
    private static double adjusted(double actual, double noise)
    {
    	double toReturn = actual;
    	
    	do
    	{
    		 toReturn = actual + (1 - 2 * myRandom.nextGaussian()) * noise;
    	}
    	while (0.0 > toReturn || toReturn > 1.0);
    	
    	return toReturn;
    }
}